package org.misty.blog.service.impl;

import org.misty.blog.dao.ArticleRepository;
import org.misty.blog.exception.NotFoundException;
import org.misty.blog.po.Article;
import org.misty.blog.service.ArticleService;
import org.misty.blog.vo.ArticleQuery;
import org.springframework.beans.BeanUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.thymeleaf.util.StringUtils;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Transactional;
import java.util.*;

@Service
public class ArticleServiceImpl implements ArticleService {
    private final ArticleRepository articleRepository;

    public ArticleServiceImpl(ArticleRepository articleRepository) {
        this.articleRepository = articleRepository;
    }

    @Override
    public Optional<Article> getArticle(Long id) {
        return articleRepository.findById(id);
    }

    @Transactional
    @Override
    public Optional<Article> viewArticle(Long id) {
        Optional<Article> opt = articleRepository.findById(id);
        if (opt.isPresent() && opt.get().isPublished()) {
            Article article = opt.get();
            article.setViews(article.getViews() + 1);
            articleRepository.save(article);
            return opt;
        } else {
            return Optional.empty();
        }
    }

    @Override
    public Page<Article> listArticles(Pageable pageable, ArticleQuery search) {
        return articleRepository.findAll(toSpec(search), pageable);
    }

    @Override
    public Page<Article> listArticles(Pageable pageable) {
        return articleRepository.findAll(pageable);
    }

    @Override
    public Page<Article> listArticlesByTagId(Pageable pageable, Long tagId) {
        List<Long> tagIds = new ArrayList<>();
        tagIds.add(tagId);
        return articleRepository.findAllByTagsIdIn(tagIds, pageable);
    }

    @Override
    public List<Article> listTop(int n) {
        Sort sort = Sort.by(Sort.Direction.DESC, "updateTime");
        Pageable pageable = PageRequest.of(0, 10, sort);
        return articleRepository.listTop(pageable);
    }

    @Override
    public Map<String, List<Article>> archiveArticles() {
        List<String> years = articleRepository.findGroupYear();
        Map<String, List<Article>> map = new LinkedHashMap<>();
        years.forEach(y -> map.put(y, articleRepository.findByYear(y)));
        return map;
    }

    @Override
    public long countArticle() {
        return articleRepository.count();
    }

    @Transactional
    @Override
    public Article saveArticle(Article article) {
        article.setCreateTime(new Date());
        article.setViews(0);
        article.setUpdateTime(new Date());
        return articleRepository.save(article);
    }

    @Transactional
    @Override
    public Article updateArticle(Long id, Article article) {
        Optional<Article> old = articleRepository.findById(id);
        old.orElseThrow(() -> new NotFoundException(String.format("文章没有找到: <%d>", id)));

        Article updated = old.get();
        BeanUtils.copyProperties(article, updated, "id", "createTime");
        updated.setUpdateTime(new Date());
        return articleRepository.save(updated);
    }

    @Transactional
    @Override
    public void deleteArticle(Long id) {
        articleRepository.deleteById(id);
    }

    private Specification<Article> toSpec(ArticleQuery query) {
        return (Root<Article> root, CriteriaQuery<?> q, CriteriaBuilder b) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (!StringUtils.isEmptyOrWhitespace(query.getTitle())) {
                predicates.add(b.like(root.get("title"), "%" + query.getTitle().trim() + "%"));
            }
            if (query.getCategoryId() != null) {
                predicates.add(b.equal(root.get("category").get("id"), query.getCategoryId()));
            }
            if (query.getRecommend() != null) {
                predicates.add(b.equal(root.get("recommend"), query.getRecommend()));
            }
            if (query.getPublished() != null) {
                predicates.add(b.equal(root.get("published"), query.getPublished()));
            }
            if (!StringUtils.isEmptyOrWhitespace(query.getSearch())) {
                String s = query.getSearch().trim();
                predicates.add(b.or(
                        b.like(root.get("title"), "%" + s + "%"),
                        b.like(root.get("content"), "%" + s + "%"),
                        b.like(root.get("description"), "%" + s + "%")
                ));
            }
            q.where(predicates.toArray(new Predicate[0]));
            return null;
        };
    }
}
